Attribute VB_Name = "MLiteTimer"
Option Explicit

Private Declare Function SetTimer Lib "user32" (ByVal hwnd As Long, ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
Private Declare Function KillTimer Lib "user32" (ByVal hwnd As Long, ByVal nIDEvent As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (dest As Any, Source As Any, ByVal bytes As Long)

Private Const WM_TIMER = &H113

Private mobjTimers As VBA.Collection

Private Sub TimerProc(ByVal hwnd As Long, ByVal uMsg As Long, ByVal idEvent As Long, ByVal dwTime As Long)
    On Error GoTo ErrorHandler
    Dim objTimer As CLiteTimer
    ' Make sure that the message is WM_TIMER.
    If uMsg = WM_TIMER Then
        For Each objTimer In mobjTimers
            ' Execute the callback method in the class.
            objTimer.TimerCallBack idEvent
        Next objTimer
    End If
    Exit Sub
ErrorHandler:
    'Debug.Print "TimerProc Error " & Err.Number & ": " & Err.Description
End Sub

Public Function StartTimer(ByVal objTimer As CLiteTimer, ByVal lngInterval As Long, ByVal lngTimerID As Long) As Long
    On Error GoTo ErrorHandler
    Dim lngTimerPtr As Long
    Dim objTimerCopy As CLiteTimer
    ' Make sure there is a parent object.
    Debug.Assert Not (objTimer Is Nothing)
    ' Create the collection to store the timers if it hasn't been already.
    If mobjTimers Is Nothing Then
        Set mobjTimers = New VBA.Collection
    End If
    ' Check to see if the timer is already running.
    If lngTimerID = 0 Then
        ' No timer is running.
        ' Was an interval specified?
        If lngInterval > 0 Then
            ' Everything is okay.
            ' Now create the timer.
            lngTimerID = SetTimer(0, 0, lngInterval, AddressOf TimerProc)
            ' Get a pointer to the object. This enables the use of weak pointers.
            lngTimerPtr = ObjPtr(objTimer)
            ' Copy the pointer to another object. This object
            ' will be used from now on to reference the parent.
            CopyMemory objTimerCopy, lngTimerPtr, 4
            mobjTimers.Add objTimerCopy, "T" & lngTimerID
            'Debug.Print "StartTimer", StartTimer, lngInterval
        End If
    End If
    StartTimer = lngTimerID
    Exit Function
ErrorHandler:
    'Debug.Print "StartTimer Error " & Err.Number & ": " & Err.Description
End Function

Public Sub StopTimer(ByRef lngTimerID As Long)
    On Error GoTo ErrorHandler
    Dim objTimerCopy As CLiteTimer
    If Not (mobjTimers Is Nothing) Then
        ' Is the timer running?
        If TimerRunning(lngTimerID) Then
            ' The timer is running. Kill it.
            If KillTimer(0, lngTimerID) <> 0 Then
                ' Timer killed.
                ' Get a reference to the parent object.
                ' This needs to be overwritten.
                Set objTimerCopy = mobjTimers("T" & lngTimerID)
                ' Remove the parent from the collection.
                ' This will not destroy the object as we
                ' still have a reference.
                mobjTimers.Remove "T" & lngTimerID
                ' Now finally destroy the reference to the object.
                ' Do not set the object to nothing as this will
                ' decrease the refcount which will cause VB to crash.
                CopyMemory objTimerCopy, 0&, 4
                lngTimerID = 0
                If mobjTimers.Count = 0 Then
                    Set mobjTimers = Nothing
                End If
            End If
        End If
    End If
    Exit Sub
ErrorHandler:
    'Debug.Print "StopTimer Error " & Err.Number & ": " & Err.Description
End Sub

Public Property Get TimerRunning(ByVal lngTimerID As Long) As Boolean
    On Error GoTo ErrorHandler
    TimerRunning = (lngTimerID <> 0)
    Exit Property
ErrorHandler:
    'Debug.Print "TimerRunning Get Error " & Err.Number & ": " & Err.Description
End Property
